Skip to content

S01-16 JavaSE-集合

[TOC]

集合的理解和好处

数组的不足

  1. 长度开始时必须指定,而且一旦指定,不能更改
  2. 保存的必须为同一类型的元素
  3. 增加/删除元素操作麻烦

数组扩容示意代码

java
Person[] pers = new Person[1];// 大小是1
pers[0] = new Person();
// 增加新的Person对象
Person[] pers2 = new Person[pers.length + 1];// 新创建数组
// 拷贝pers数组的元素到pers2
for (int i = 0; i < pers.length; i++) {
    pers2[i] = pers[i];
}
pers2[pers2.length - 1] = new Person();// 添加新的对象

集合的好处

  1. 可以动态保存任意多个对象,使用比较方便
  2. 提供了一系列方便的操作对象的方法:add、remove、set、get等
  3. 增加/删除元素简洁

集合的框架体系

Java 的集合类主要分为两大类:

  1. 单列集合(Collection):
    • List(有序、可重复):ArrayList、LinkedList、Vector
    • Set(无序、不可重复):HashSet、TreeSet
  2. 双列集合(Map):HashMap、TreeMap、Hashtable、LinkedHashMap、Properties

集合框架图

Iterable
└── Collection
    ├── List
    │   ├── ArrayList
    │   ├── LinkedList
    │   └── Vector
    └── Set
        ├── HashSet
        └── TreeSet
└── Map
    ├── HashMap
    ├── TreeMap
    ├── Hashtable
    ├── LinkedHashMap
    └── Properties

Collection_.java

java
package com.hspedu.collection_;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class Collection_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        // 单列集合
        Collection arrayList = new ArrayList();
        arrayList.add("jack");
        arrayList.add("tom");

        // 双列集合
        Map hashMap = new HashMap();
        hashMap.put("NO1", "北京");
        hashMap.put("NO2", "上海");
    }
}

第601页

Collection 接口和常用方法

Collection 接口实现类的特点

  1. Collection实现子类可以存放多个元素,每个元素可以是Object
  2. 有些Collection的实现类,可以存放重复的元素(List),有些不可以(Set)
  3. 有些Collection的实现类,是有序的(List),有些不是有序(Set)
  4. Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的

Collection 接口常用方法

方法说明
add(Object ele)添加单个元素
addAll(Collection eles)添加多个元素
remove(Object ele)删除指定元素
removeAll(Collection eles)删除多个元素
contains(Object ele)查找元素是否存在
containsAll(Collection eles)查找多个元素是否都存在
size()获取元素个数
isEmpty()判断是否为空
clear()清空集合

CollectionMethod.java

java
package com.hspedu.collection_;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class CollectionMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();

        // 1. add:添加单个元素
        list.add("jack");
        list.add(10);// 自动装箱
        list.add(true);
        list.add("jack");// 可重复
        System.out.println("list=" + list);// [jack, 10, true, jack]

        // 2. remove:删除指定元素
        list.remove(true);// 删除元素true
        // list.remove(0);// 删除索引0的元素
        System.out.println("list=" + list);// [jack, 10, jack]

        // 3. contains:查找元素是否存在
        System.out.println(list.contains("jack"));// true

        // 4. size:获取元素个数
        System.out.println(list.size());// 3

        // 5. isEmpty:判断是否为空
        System.out.println(list.isEmpty());// false

        // 6. addAll:添加多个元素
        ArrayList list2 = new ArrayList();
        list2.add("红楼梦");
        list2.add("三国演义");
        list.addAll(list2);
        System.out.println("list=" + list);// [jack, 10, jack, 红楼梦, 三国演义]

        // 7. containsAll:查找多个元素是否都存在
        System.out.println(list.containsAll(list2));// true

        // 8. removeAll:删除多个元素
        list.removeAll(list2);
        System.out.println("list=" + list);// [jack, 10, jack]

        // 9. clear:清空
        list.clear();
        System.out.println("list=" + list);// []
    }
}

第603页

Collection 接口遍历元素方式1-使用Iterator(迭代器)

基本介绍

  1. Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
  2. 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
  3. Iterator仅用于遍历集合,Iterator本身并不存放对象。

迭代器的执行原理

java
Iterator iterator = coll.iterator();// 得到集合的迭代器
while(iterator.hasNext()){// 判断是否还有下一个元素
    // next()作用:1.下移 2.将下移以后集合位置上的元素返回
    Object obj = iterator.next();
    System.out.println(obj);
}

Iterator接口的方法

方法说明
hasNext()判断是否还有下一个元素,返回boolean
next()返回迭代的下一个元素
remove()移除迭代器返回的最后一个元素(可选操作)

老韩提示:在调用iterator.next()方法之前必须要调用iterator.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next()会抛出NoSuchElementException异常。

迭代器使用案例 CollectionIterator.java

java
package com.hspedu.collection_;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class CollectionIterator {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        Collection col = new ArrayList();
        col.add(new Book("红楼梦", "曹雪芹", 100.0));
        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));

        // 1. 得到迭代器
        Iterator iterator = col.iterator();

        // 2. 第一次遍历
        System.out.println("===第一次遍历===");
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj=" + obj);
        }

        // 3. 再次遍历需要重置迭代器
        iterator = col.iterator();
        System.out.println("===第二次遍历===");
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj=" + obj);
        }
    }
}

class Book {
    private String name;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

第609页

Collection 接口遍历对象方式2-for 循环增强

基本语法

java
for(元素类型 元素名: 集合名或数组名){
    访问元素
}

特点

增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。

案例演示

java
// 增强for遍历Collection
for (Object object : col) {
    System.out.println(object);
}

课堂练习 CollectionExercise.java

题目

  1. 创建3个Dog {name, age}对象,放入到ArrayList中,赋给List引用
  2. 用迭代器和增强for循环两种方式来遍历
  3. 重写Dog 的toString方法,输出name和age

代码实现

java
package com.hspedu.collection_;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class CollectionExercise {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Dog("小黑", 3));
        list.add(new Dog("大壮", 100));
        list.add(new Dog("大黄", 8));

        // 1. 增强for遍历
        System.out.println("===增强for遍历===");
        for (Object dog : list) {
            System.out.println("dog=" + dog);
        }

        // 2. 迭代器遍历
        System.out.println("===迭代器遍历===");
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object dog = iterator.next();
            System.out.println("dog=" + dog);
        }
    }
}

class Dog {
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

第612页

List 接口和常用方法

List 接口基本介绍

  1. List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复。
  2. List集合中的每个元素都有其对应的顺序索引,即支持索引。
  3. List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  4. JDK API中List接口的实现类有:ArrayList、LinkedList和Vector。

案例 List_.java

java
package com.hspedu.list_;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class List_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        // 1. 元素有序、可重复
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("mary");
        list.add("hsp");
        list.add("tom");// 重复元素
        System.out.println("list=" + list);// [jack, tom, mary, hsp, tom]

        // 2. 支持索引
        System.out.println(list.get(3));// hsp
    }
}

第614页

List 接口的常用方法

方法说明
void add(int index, Object ele)在index位置插入ele元素
boolean addAll(int index, Collection eles)从index位置开始将eles中的所有元素添加进来
Object get(int index)获取指定index位置的元素
int indexOf(Object obj)返回obj在集合中首次出现的位置
int lastIndexOf(Object obj)返回obj在当前集合中末次出现的位置
Object remove(int index)移除指定index位置的元素,并返回此元素
Object set(int index, Object ele)设置指定index位置的元素为ele,相当于是替换
List subList(int fromIndex, int toIndex)返回从fromIndex到toIndex位置的子集合(左闭右开)

ListMethod.java

java
package com.hspedu.list_;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class ListMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("张三丰");
        list.add("贾宝玉");
        System.out.println("list=" + list);// [张三丰, 贾宝玉]

        // 1. add(int index, Object ele)
        list.add(1, "韩顺平");
        System.out.println("list=" + list);// [张三丰, 韩顺平, 贾宝玉]

        // 2. addAll(int index, Collection eles)
        List list2 = new ArrayList();
        list2.add("jack");
        list2.add("tom");
        list.addAll(1, list2);
        System.out.println("list=" + list);// [张三丰, jack, tom, 韩顺平, 贾宝玉]

        // 3. indexOf(Object obj)
        System.out.println(list.indexOf("tom"));// 2

        // 4. lastIndexOf(Object obj)
        list.add("韩顺平");
        System.out.println("list=" + list);// [张三丰, jack, tom, 韩顺平, 贾宝玉, 韩顺平]
        System.out.println(list.lastIndexOf("韩顺平"));//

Set 接口

Set 接口的特点

  1. 无序(添加和取出的顺序不一致),没有索引
  2. 不允许重复元素,所以最多包含一个 null
  3. JDK API 中 Set 接口的实现类有:HashSetTreeSet
  4. 所有超级接口:Collection<E>Iterable<E>
  5. 所有已知子接口:NavigableSet<E>SortedSet<E>

Set 接口的常用方法

和 List 接口一样,Set 接口也是 Collection 的子接口,因此常用方法和 Collection 接口一致。

Set 接口的遍历方式

同 Collection 的遍历方式(Set 是 Collection 子接口):

  1. 迭代器
  2. 增强 for
  3. 不能使用索引方式获取元素

Set 接口的常用方法举例(SetMethod.java)

java
package com.hspedu.set_;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

@SuppressWarnings({"all"})
public class SetMethod {
    // 老韩解读
    // 1. 以 Set 接口的实现类 HashSet 来讲解 Set 接口的方法
    // 2. set 接口的实现类的对象(Set 接口对象), 不能存放重复的元素, 可以添加一个 null
    // 3. set 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
    public static void main(String[] args) {
        Set set = new HashSet();
        // 添加元素
        set.add("john");
        set.add("lucy");
        set.add("john");// 重复元素,添加失败
        set.add("jack");
        set.add("hsp");
        set.add("mary");
        set.add(null);
        set.add(null);// 再次添加 null,失败

        // 多次打印,验证取出顺序固定(虽与添加顺序不同)
        for (int i = 0; i < 10; i++) {
            System.out.println("set=" + set);
        }

        // 遍历方式1:迭代器
        System.out.println("=====使用迭代器====");
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println("obj=" + obj);
        }

        // 删除 null
        set.remove(null);

        // 遍历方式2:增强 for
        System.out.println("=====增强 for====");
        for (Object o : set) {
            System.out.println("o=" + o);
        }

        // 注意:set 接口对象不能通过索引获取元素
    }
}

Set 接口实现类 - HashSet

HashSet 的全面说明

  1. HashSet 实现了 Set 接口
  2. HashSet 实际上是 HashMap(源码:public HashSet() { map = new HashMap<>(); }
  3. 可以存放 null 值,但只能有一个 null
  4. 不保证存放元素的顺序和取出顺序一致
  5. 不能有重复元素/对象

HashSet 案例说明(HashSet01.java)

java
package com.hspedu.set_;
import java.util.HashSet;
import java.util.Set;

@SuppressWarnings({"all"})
public class HashSet01 {
    public static void main(String[] args) {
        HashSet set = new HashSet();

        // add 方法返回 boolean:添加成功返回 true,失败返回 false
        System.out.println(set.add("john"));// T
        System.out.println(set.add("lucy"));// T
        System.out.println(set.add("john"));// F(重复)
        System.out.println(set.add("jack"));// T
        System.out.println(set.add("Rose"));// T
        System.out.println("set=" + set);

        // 删除元素
        set.remove("john");
        System.out.println("set=" + set);

        // 重置 set
        set = new HashSet();
        System.out.println("set=" + set);
        set.add("lucy");
        set.add("lucy");// 重复,添加失败
        // 添加自定义对象(未重写 hashCode 和 equals)
        set.add(new Dog("tom"));// OK
        set.add(new Dog("tom"));// OK(视为不同对象)
        System.out.println("set=" + set);

        // 经典面试题:String 类型重复添加失败
        set.add(new String("hsp"));// OK
        set.add(new String("hsp"));// 失败(String 重写了 hashCode 和 equals)
        System.out.println("set=" + set);
    }
}

// 自定义 Dog 类
class Dog {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" + "name='" + name + '\'' + '}';
    }
}

HashSet 底层机制说明

核心原理

HashSet 底层是 HashMap,HashMap 底层是 数组 + 链表 + 红黑树(Java 8)。添加元素的核心流程:

  1. 先获取元素的哈希值(hashCode() 方法)
  2. 对哈希值进行运算,得出索引值(确定存储位置)
  3. 若该位置无元素,直接存放
  4. 若该位置有元素,进行 equals 判断:
    • 相等:不添加
    • 不相等:以链表形式添加到末尾
  5. Java 8 中:若链表长度 ≥ 8 且数组大小 ≥ 64,链表转为红黑树(树化)

源码解读(HashSetSource.java)

java
package com.hspedu.set_;
import java.util.HashSet;

@SuppressWarnings({"all"})
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");// 第 1 次 add
        hashSet.add("php");// 第 2 次 add
        hashSet.add("java");// 重复添加
        System.out.println("set=" + hashSet);
    }
}

// 老韩对 HashSet 源码解读
/*
1. 执行 HashSet() 构造器:初始化 HashMap
   public HashSet() {
       map = new HashMap<>();
   }

2. 执行 add(E e):调用 HashMap 的 put 方法
   public boolean add(E e) {
       return map.put(e, PRESENT) == null; // PRESENT 是静态常量 Object 对象
   }

3. 执行 HashMap 的 put(K key, V value):计算 key 的 hash 值
   public V put(K key, V value) {
       return putVal(hash(key), key, value, false, true);
   }

4. 执行 hash(key):哈希值运算(h = key.hashCode() ^ (h >>> 16))
5. 执行 putVal():核心逻辑(扩容、节点添加、树化等)
*/

HashSet 扩容和树化机制

  1. 第一次添加元素时,数组扩容到 16,临界值(threshold)= 16 × 加载因子(0.75)= 12
  2. 当数组使用到临界值 12 时,扩容到 32,新临界值 = 32 × 0.75 = 24,依次类推
  3. 树化条件:
    • 链表长度 ≥ TREEIFY_THRESHOLD(默认 8)
    • 数组大小 ≥ MIN_TREEIFY_CAPACITY(默认 64)
    • 不满足则先扩容,不树化

HashSet 课堂练习1(HashSetExercise.java)

需求:定义 Employee 类(name, age),当 name 和 age 相同时视为同一员工,不能重复添加到 HashSet。

java
package com.hspedu.set_;
import java.util.HashSet;
import java.util.Objects;

@SuppressWarnings({"all"})
public class HashSetExercise {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add(new Employee("milan", 18));// OK
        hashSet.add(new Employee("smith", 28));// OK
        hashSet.add(new Employee("milan", 18));// 失败(重复)
        System.out.println("hashSet=" + hashSet);
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写 equals 和 hashCode,基于 name 和 age 判断
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Employee{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

HashSet 课后练习2

需求:定义 Employee 类(name, sal, birthday),birthday 为 MyDate 类(year, month, day)。当 name 和 birthday 相同时视为同一员工,不能重复添加到 HashSet。

Set 接口实现类 - LinkedHashSet

LinkedHashSet 的全面说明

  1. LinkedHashSet 是 HashSet 的子类
  2. 底层是 LinkedHashMap,维护 数组 + 双向链表
  3. 根据元素的 hashCode 确定存储位置,同时用链表维护插入顺序(遍历顺序与插入顺序一致)
  4. 不允许重复元素

LinkedHashSet 底层机制示意图

LinkedHashSet 底层机制示意图

  • 维护哈希表(数组)和双向链表
  • 每个节点有 before 和 after 属性,形成双向链表
  • 添加元素时:先通过 hash 确定数组位置,再加入双向链表(重复元素不添加)

LinkedHashSet 课后练习题(LinkedHashSetExercise.java)

需求:Car 类(name, price),name 和 price 相同时视为相同元素,不能添加到 LinkedHashSet。

java
package com.hspedu.set_;
import java.util.LinkedHashSet;
import java.util.Objects;

@SuppressWarnings({"all"})
public class LinkedHashSetExercise {
    public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new Car("奥拓", 1000));// OK
        linkedHashSet.add(new Car("奥迪", 300000));// OK
        linkedHashSet.add(new Car("法拉利", 10000000));// OK
        linkedHashSet.add(new Car("奥迪", 300000));// 失败(重复)
        linkedHashSet.add(new Car("保时捷", 70000000));// OK
        linkedHashSet.add(new Car("奥迪", 300000));// 失败(重复)
        System.out.println("linkedHashSet=" + linkedHashSet);
    }
}

class Car {
    private String name;
    private double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }

    // 重写 equals 和 hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, price);
    }

    @Override
    public String toString() {
        return "\nCar{" + "name='" + name + '\'' + ", price=" + price + '}';
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}

Map 接口和常用方法

Map 接口实现类的特点

  1. Map 与 Collection 并列存在,用于存储 Key-Value 映射关系数据
  2. Key 和 Value 可以是任意引用类型,封装到 HashMap$Node 对象中
  3. Key 不允许重复(原理同 HashSet)
  4. Value 可以重复
  5. Key 可以为 null(最多一个),Value 可以为 null(多个)
  6. 常用 String 作为 Key
  7. Key 和 Value 是单向一对一关系(通过 Key 总能找到唯一 Value)

Map 接口常用方法(MapMethod.java)

java
package com.hspedu.map_;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings({"all"})
public class MapMethod {
    public static void main(String[] args) {
        Map map = new HashMap();

        // 1. put:添加/替换元素(Key 存在则替换 Value)
        map.put("no1", "韩顺平");// K-V
        map.put("no2", "张无忌");// K-V
        map.put("no1", "张三丰");// 替换 Value
        map.put("no3", "张三丰");// Value 重复
        map.put(null, null);// K-V
        map.put(null, "abc");// 替换 null Key 的 Value
        map.put("no4", null);// Value 为 null
        map.put("no5", null);// Value 为 null
        map.put(1, "赵敏");// Key 为 Integer
        map.put(new Object(), "金毛狮王");// Key 为 Object
        System.out.println("map=" + map);

        // 2. remove:根据 Key 删除映射关系
        map.remove(null);
        System.out.println("map=" + map);

        // 3. get:根据 Key 获取 Value
        Object val = map.get("no2");
        System.out.println("val=" + val);// 张无忌

        // 4. size:获取元素个数
        System.out.println("k-v 个数=" + map.size());

        // 5. isEmpty:判断是否为空
        System.out.println(map.isEmpty());// false

        // 6. containsKey:判断 Key 是否存在
        System.out.println("是否包含 Key 'hsp'=" + map.containsKey("hsp"));// false

        // 7. clear:清空所有 K-V(注释避免影响后续操作)
        // map.clear();
    }
}

class Book {
    private String name;
    private int num;

    public Book(String name, int num) {
        this.name = name;
        this.num = num;
    }
}

Map 接口遍历方法(MapFor.java)

Map 遍历有 3 种核心方式:

  1. 遍历所有 Key,通过 Key 获取 Value
  2. 直接遍历所有 Value
  3. 遍历 Key-Value 对(EntrySet)
java
package com.hspedu.map_;
import java.util.*;

@SuppressWarnings({"all"})
public class MapFor {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("邓超", "孙俪");
        map.put("王宝强", "马蓉");
        map.put("宋喆", "马蓉");
        map.put("刘令博", null);
        map.put(null, "刘亦菲");
        map.put("鹿晗", "关晓彤");

        // 第一组:遍历所有 Key -> 获取 Value
        Set keySet = map.keySet();
        // 方式1:增强 for
        System.out.println("-----第一种方式(KeySet 增强 for)-------");
        for (Object key : keySet) {
            System.out.println(key + "-" + map.get(key));
        }
        // 方式2:迭代器
        System.out.println("-----第二种方式(KeySet 迭代器)-------");
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            System.out.println(key + "-" + map.get(key));
        }

        // 第二组:直接遍历所有 Value
        Collection values = map.values();
        // 方式1:增强 for
        System.out.println("-----第三种方式(Values 增强 for)-------");
        for (Object value : values) {
            System.out.println(value);
        }
        // 方式2:迭代器
        System.out.println("-----第四种方式(Values 迭代器)-------");
        Iterator iterator2 = values.iterator();
        while (iterator2.hasNext()) {
            Object value = iterator2.next();
            System.out.println(value);
        }

        // 第三组:遍历 EntrySet(Key-Value 对)
        Set entrySet = map.entrySet();
        // 方式1:增强 for
        System.out.println("-----第五种方式(EntrySet 增强 for)-------");
        for (Object entry : entrySet) {
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        // 方式2:迭代器
        System.out.println("-----第六种方式(EntrySet 迭代器)-------");
        Iterator iterator3 = entrySet.iterator();
        while (iterator3.hasNext()) {
            Object entry = iterator3.next();
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
    }
}

Map 接口课堂练习(MapExercise.java)

需求:使用 HashMap 存储员工(Key:员工 id,Value:员工对象),遍历并显示工资 > 18000 的员工(至少两种遍历方式)。

java
package com.hspedu.map_;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

@SuppressWarnings({"all"})
public class MapExercise {
    public static void main(String[] args) {
        Map hashMap = new HashMap();
        // 添加员工对象
        hashMap.put(1, new Emp("jack", 30000, 1));
        hashMap.put(2, new Emp("tom", 21000, 2));
        hashMap.put(3, new Emp("milan", 12000, 3));

        // 方式1:KeySet + 增强 for
        System.out.println("====第一种遍历方式(KeySet 增强 for)====");
        Set keySet = hashMap.keySet();
        for (Object key : keySet) {
            Emp emp = (Emp) hashMap.get(key);
            if (emp.getSal() > 18000) {
                System.out.println(emp);
            }
        }

        // 方式2:EntrySet + 迭代器
        System.out.println("====第二种遍历方式(EntrySet 迭代器)====");
        Set entrySet = hashMap.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry) iterator.next();
            Emp emp = (Emp) entry.getValue();
            if (emp.getSal() > 18000) {
                System.out.println(emp);
            }
        }
    }
}

// 员工类
class Emp {
    private String name;
    private double sal;
    private int id;

    public Emp(String name, double sal, int id) {
        this.name = name;
        this.sal = sal;
        this.id = id;
    }

    // getter/setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getSal() { return sal; }
    public void setSal(double sal) { this.sal = sal; }
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }

    @Override
    public String toString() {
        return "Emp{" + "name='" + name + '\'' + ", sal=" + sal + ", id=" + id + '}';
    }
}

Map 接口实现类 - HashMap

HashMap 小结

  1. Map 接口常用实现类:HashMap、Hashtable、Properties
  2. HashMap 是 Map 接口使用频率最高的实现类
  3. 以 Key-Value 对存储(HashMap$Node 类型),Node 实现 Map.Entry 接口
  4. Key 不能重复,Value 可以重复;允许 null Key(1 个)和 null Value(多个)
  5. 相同 Key 添加时,Value 会覆盖(Key 不变)
  6. 不保证映射顺序(底层哈希表),Java 8 底层是 数组 + 链表 + 红黑树
  7. 线程不安全(无 synchronized 修饰),效率高

HashMap 底层机制及源码剖析

底层结构

  • JDK 7:数组 + 链表
  • JDK 8:数组 + 链表 + 红黑树(链表长度 ≥ 8 且数组大小 ≥ 64 时树化)

扩容机制

  1. 底层维护 Node 数组 table,默认初始化为 null
  2. 加载因子(loadFactor)默认 0.75
  3. 第一次添加元素时,数组扩容到 16,临界值 = 16 × 0.75 = 12
  4. 后续扩容:数组大小翻倍(16 → 32 → 64...),临界值也翻倍
  5. 当数组使用到临界值时,触发扩容

源码核心流程(HashMapSource1.java)

java
package com.hspedu.map_;
import java.util.HashMap;

@SuppressWarnings({"all"})
public class HashMapSource1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java", 10);// OK
        map.put("php", 10);// OK
        map.put("java", 20);// 替换 Value
        System.out.println("map=" + map);
    }
}

// 源码解读核心步骤
/*
1. 执行 new HashMap():初始化加载因子 loadFactor = 0.75,table = null
2. 执行 put(K key, V value):调用 hash(key) 计算哈希值
   public V put(K key, V value) {
       return putVal(hash(key), key, value, false, true);
   }
3. 执行 hash(key):h = key.hashCode() ^ (h >>> 16)(哈希值优化)
4. 执行 putVal():
   a. 若 table 为 null 或长度为 0,扩容到 16
   b. 根据 hash 值计算索引 i = (n - 1) & hash
   c. 若 table[i] 为 null,直接创建 Node 放入
   d. 若 table[i] 不为 null:
      i. 若 Key 相同,替换 Value
      ii. 若为红黑树节点,调用 putTreeVal 添加
      iii. 若为链表,遍历比较,无相同 Key 则添加到链表末尾;链表长度 ≥8 则树化
   e. size 超过临界值,扩容
*/

Map 接口实现类 - Hashtable

Hashtable 的基本介绍

  1. 存储 Key-Value 对,底层结构同 HashMap(JDK 8 后也支持红黑树)
  2. Key 和 Value 都不能为 null(否则抛 NullPointerException)
  3. 方法使用与 HashMap 基本一致
  4. 线程安全(方法加了 synchronized 修饰),效率低
  5. 版本:JDK 1.0 推出

Hashtable 和 HashMap 对比

特性HashMapHashtable
版本JDK 1.2JDK 1.0
线程安全(同步)不安全安全
效率
允许 null Key/Value允许(Key 1个,Value 多个)不允许

Hashtable 应用案例

java
package com.hspedu.map_;
import java.util.Hashtable;

public class HashtableExercise {
    public static void main(String[] args) {
        Hashtable table = new Hashtable();
        table.put("john", 100);// OK
        // table.put(null, 100);// 异常(null Key)
        // table.put("john", null);// 异常(null Value)
        table.put("lucy", 100);// OK
        table.put("lic", 100);// OK
        table.put("lic", 88);// 替换 Value
        System.out.println(table);
    }
}

Map 接口实现类 - Properties

基本介绍

  1. 继承自 Hashtable,实现 Map 接口
  2. 存储 Key-Value 对,Key 和 Value 都不能为 null
  3. 常用于读取配置文件(.properties)
  4. 方法使用与 Hashtable 类似

基本使用(Properties_.java)

java
package com.hspedu.map_;
import java.util.Properties;

@SuppressWarnings({"all"})
public class Properties_ {
    public static void main(String[] args) {
        Properties properties = new Properties();

        // 添加 K-V
        properties.put("john", 100);
        properties.put("lucy", 100);
        properties.put("lic", 100);
        properties.put("lic", 88);// 替换 Value
        System.out.println("properties=" + properties);

        // 根据 Key 获取 Value
        System.out.println("lic 的值=" + properties.get("lic"));// 88

        // 删除 K-V
        properties.remove("lic");
        System.out.println("properties=" + properties);

        // 修改 Value
        properties.put("john", "约翰");
        System.out.println("properties=" + properties);
    }
}

开发中如何选择集合实现类

核心选择逻辑

  1. 先判断存储类型:
    • 单列数据(一组对象):Collection 接口
      • 允许重复:List
        • 增删多:LinkedList(双向链表)
        • 改查多:ArrayList(可变数组)
      • 不允许重复:Set
        • 无序:HashSet(哈希表)
        • 有序(插入顺序):LinkedHashSet(数组 + 双向链表)
        • 排序:TreeSet(红黑树)
    • 双列数据(Key-Value):Map
      • 无序:HashMap(哈希表)
      • 有序(插入顺序):LinkedHashMap(数组 + 双向链表)
      • 排序:TreeMap(红黑树)
      • 配置文件:Properties(继承 Hashtable)

Collections 工具类

Collections 工具类介绍

  1. 操作 Set、List、Map 等集合的工具类
  2. 提供静态方法:排序、查询、修改等

常用方法(Collections_.java)

java
package com.hspedu.collections_;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

@SuppressWarnings({"all"})
public class Collections_ {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");
        list.add("tom");

        // 1. reverse(List):反转列表
        Collections.reverse(list);
        System.out.println("反转后:" + list);

        // 2. shuffle(List):随机排序(每次结果不同)
        System.out.println("随机排序:");
        for (int i = 0; i < 3; i++) {
            Collections.shuffle(list);
            System.out.println(list);
        }

        // 3. sort(List):自然排序(默认升序)
        Collections.sort(list);
        System.out.println("自然排序后:" + list);

        // 4. sort(List, Comparator):定制排序(按字符串长度降序)
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o2).length() - ((String) o1).length();
            }
        });
        System.out.println("按字符串长度降序:" + list);

        // 5. swap(List, int, int):交换指定索引元素
        Collections.swap(list, 0, 1);
        System.out.println("交换索引 0 和 1 后:" + list);

        // 6. max(Collection):自然顺序最大值
        System.out.println("自然顺序最大值:" + Collections.max(list));

        // 7. max(Collection, Comparator):定制排序最大值(长度最大)
        Object maxLenObj = Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String) o1).length() - ((String) o2).length();
            }
        });
        System.out.println("长度最大的元素:" + maxLenObj);

        // 8. frequency(Collection, Object):元素出现次数
        System.out.println("tom 出现次数:" + Collections.frequency(list, "tom"));

        // 9. copy(List dest, List src):复制 src 到 dest(dest 需提前初始化大小)
        List dest = new ArrayList();
        for (int i = 0; i < list.size(); i++) {
            dest.add("");// 初始化 dest 大小
        }
        Collections.copy(dest, list);
        System.out.println("复制后 dest:" + dest);

        // 10. replaceAll(List, Object oldVal, Object newVal):替换所有旧值
        Collections.replaceAll(list, "tom", "汤姆");
        System.out.println("替换后 list:" + list);
    }
}

本章作业

编程题 Homework01.java

需求

  1. 封装新闻类(标题、内容),重写 toString 只打印标题
  2. 带参构造器初始化标题,实例化 2 个新闻对象
  3. 添加到 ArrayList,倒序遍历
  4. 标题超过 15 字时,保留前 15 字 + "..."
  5. 打印处理后的标题

编程题 Homework02.java

需求:使用 ArrayList 操作 Car 类(name, price),实现:

  • add、remove、contains、size、isEmpty、clear、addAll、containsAll、removeAll
  • 增强 for 和迭代器遍历(重写 toString)

编程题 Homework03.java

需求

  1. 使用 HashMap 存储员工(姓名-工资):jack-650,tom-1200,smith-2900
  2. 修改 jack 工资为 2600
  3. 所有员工加薪 100 元
  4. 遍历所有员工
  5. 遍历所有工资

简答题 Homework04.java

问题:HashSet 和 TreeSet 分别如何实现去重?

  • HashSet:hashCode() + equals()。先通过 hash 值确定索引,索引位置无元素则添加;有元素则通过 equals 比较,相等则不添加。
  • TreeSet:
    • 传入 Comparator 匿名对象:通过 compare() 方法判断,返回 0 则视为重复。
    • 未传入 Comparator:通过元素实现的 Comparable 接口的 compareTo() 方法判断。

代码分析题 Homework05.java

代码

java
TreeSet treeSet = new TreeSet();
treeSet.add(new Person());

问题:是否抛出异常?说明原因。

  • 答:会抛出 ClassCastException。TreeSet 底层需要排序,Person 类未实现 Comparable 接口,也未传入 Comparator,无法比较元素大小,导致异常。

代码输出题 Homework06.java

已知:Person 类按 id 和 name 重写了 hashCode 和 equals 方法。 代码

java
HashSet set = new HashSet();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC";// 修改 p1 的 name
set.remove(p1);// 能否删除?
System.out.println(set);
set.add(new Person(1001, "CC"));// 能否添加?
System.out.println(set);
set.add(new Person(1001, "AA"));// 能否添加?
System.out.println(set);

输出

[Person{id=1001, name='CC'}, Person{id=1002, name='BB'}]
[Person{id=1001, name='CC'}, Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
[Person{id=1001, name='CC'}, Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]

原因:p1 添加时的 hash 值基于原 name(AA),修改 name 后 hash 值改变,remove 时无法找到原索引,删除失败;后续添加的 Person(1001, "CC") 与修改后的 p1 hash 值相同,但 equals 比较时因 id 和 name 都相同,会视为重复吗?需注意:修改 p1 的 name 后,p1 的 hashCode 改变,但集合中存储的 p1 节点的 hash 值未更新,因此新添加的 Person(1001, "CC") 会计算新的 hash 值,可能与原 p1 所在索引不同,导致添加成功。

Vector 和 ArrayList 的比较 Homework07.java

特性ArrayListVector
底层结构可变数组可变数组
版本JDK 1.2JDK 1.0
线程安全(同步)不安全,效率高安全(方法加 synchronized),效率低
扩容倍数无参构造:10 → 1.5 倍扩容;有参构造:1.5 倍扩容无参构造:10 → 2 倍扩容;有参构造:2 倍扩容